package internal.parser;

import java.util.Collection;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;

import parser.IParse.IArithmeticExpression;
import parser.IParse.IAssignment;
import parser.IParse.IBooleanExpression;
import parser.IParse.ICompoundStatement;
import parser.IParse.IDecimalLiteral;
import parser.IParse.IExpression;
import parser.IParse.IFunction;
import parser.IParse.IIfThenElse;
import parser.IParse.IIntegerLiteral;
import parser.IParse.ILiteral;
import parser.IParse.IParseNode;
import parser.IParse.IParseNodeVisitor;
import parser.IParse.IParser;
import parser.IParse.IReturn;
import parser.IParse.ISimpleStatement;
import parser.IParse.ITerm;
import parser.IParse.IVariable;
import parser.IParse.IWhile;
import parser.IParseFactory;
import tokenizer.IToken;

public class ParseFactory implements IParseFactory {

	/* Public newX (factory) methods -- START */
	@Override
	public IBooleanExpression newBooleanExpression(
			final IExpression leftOperand, final IToken operator,
			final IExpression rightOperand) {
		return new BooleanExpression(leftOperand, operator, rightOperand);
	}

	// Prof: similarly, create a public newX method for each type of parse node
	// X, along with a private static class for X. rest of the code to be
	// written by you

	@Override
	public IArithmeticExpression newArithmeticExpression(
			IExpression leftOperand, IToken operator, IExpression rightOperand) {
		return new ArithmeticExpression(leftOperand, operator, rightOperand);
	}

	@Override
	public IAssignment newAssignment(IVariable variable, IExpression expression) {
		return new Assignment(expression, variable);
	}

	@Override
	public IIfThenElse newIfThen(IBooleanExpression ifPredicate,
			ICompoundStatement thenPart) {
		return new IfThenElse(ifPredicate, thenPart);
	}

	@Override
	public IIfThenElse newIfThenElse(IBooleanExpression ifPredicate,
			ICompoundStatement thenStatement, ICompoundStatement elseStatement) {
		return new IfThenElse(ifPredicate, thenStatement, elseStatement);
	}

	@Override
	public IWhile newWhile(IBooleanExpression whilePredicate,
			ICompoundStatement body) {
		return new While(whilePredicate, body);
	}

	@Override
	public IFunction newFunction(String name, List<IVariable> parameters,
			ICompoundStatement body) {
		return new Function(name, parameters, body);
	}

	@Override
	public ILiteral newInteger(Integer value) {
		return new IntegerLiteral(value);
	}

	@Override
	public ILiteral newDecimal(Double value) {
		return new DecimalLiteral(value);
	}

	@Override
	public IVariable newVariable(String name) {
		return new Variable(name);
	}

	@Override
	public ICompoundStatement newCompoundStatement(
			List<parser.IParse.ISimpleStatement> statements) {
		return new CompoundStatement(statements);
	}

	@Override
	public IParser newParser() {
		IParser parser = new Parser(this);
		return parser;
	}

	@Override
	public parser.IParse.IReturn newReturn(IExpression expression) {
		return new Return(expression);
	}

	@Override
	public parser.IParse.ITerm newTerm(IExpression leftOperand,
			IToken operator, IExpression rightOperand) {
		return new Term(leftOperand, operator, rightOperand);
	}

	/* Public newX (factory) methods -- END */

	/* ABSTRACT WRAPPERS -- START */
	private static abstract class AbstractNode implements IParseNode {
		private static final AtomicInteger COUNTER = new AtomicInteger(0);
		private final int id;

		AbstractNode() {

			this.id = COUNTER.incrementAndGet();
		}

		@Override
		public boolean equals(final Object o) {
			if (!(o instanceof AbstractNode)) {
				return false;
			}
			// make sure we use Variable equality if the input object is a
			// variable
			if (o instanceof Variable) {
				return o.equals(this);
			}
			// otherwise we use id-based equality
			return this.id == ((AbstractNode) o).id;
		}

		@Override
		public int hashCode() {
			return this.id;
		}

		@Override
		public int id() {
			return id;
		}

		@Override
		public String toString() {
			return String.format("nid=%d", id);
		}

		@Override
		public String varId() {
			return String.format("T_%d", id);
		}
	}

	private abstract static class Expression extends AbstractNode {
		private final IExpression leftOperand;
		private final IToken operator;
		private final IExpression rightOperand;

		Expression(final IExpression leftOperand, final IToken operator,
				final IExpression rightOperand) {
			this.leftOperand = leftOperand;
			this.operator = operator;
			this.rightOperand = rightOperand;
		}

		public IExpression leftOperand() {
			return this.leftOperand;
		}

		public IToken operator() {
			return this.operator;
		}

		public IExpression rightOperand() {
			return this.rightOperand;
		}
	}

	private abstract static class SimpleStatement extends AbstractNode
			implements ISimpleStatement {
		// Empty Wrapper
	}

	/* ABSTRACT WRAPPERS -- END */

	private static class BooleanExpression extends Expression implements
			IBooleanExpression {
		BooleanExpression(final IExpression leftOperand, final IToken operator,
				final IExpression rightOperand) {
			super(leftOperand, operator, rightOperand);
		}

		@Override
		public void accept(IParseNodeVisitor visitor) {
			if (visitor.visit(this)) {
				leftOperand().accept(visitor);
				rightOperand().accept(visitor);
			}
		}
	}

	private static class ArithmeticExpression extends Expression implements
			IArithmeticExpression {
		ArithmeticExpression(IExpression leftOperand, IToken operator,
				IExpression rightOperand) {
			super(leftOperand, operator, rightOperand);
		}

		@Override
		public void accept(IParseNodeVisitor visitor) {
			if (visitor.visit(this)) {
				leftOperand().accept(visitor);
				rightOperand().accept(visitor);
			}
		}
	}

	private static class Assignment extends SimpleStatement implements
			IAssignment {
		private final IExpression expression;
		private final IVariable variable;

		public Assignment(IExpression expression, IVariable variable) {
			this.expression = expression;
			this.variable = variable;
		}

		@Override
		public void accept(IParseNodeVisitor visitor) {
			if (visitor.visit(this)) {
				expression().accept(visitor);
				variable().accept(visitor);
			}
		}

		@Override
		public IExpression expression() {
			return expression;
		}

		@Override
		public IVariable variable() {
			return variable;
		}
	}

	private static class IfThenElse extends SimpleStatement implements
			IIfThenElse {
		private final IBooleanExpression predicate;
		private final ICompoundStatement thenStatement;
		private ICompoundStatement elseStatement;

		IfThenElse(IBooleanExpression predicate,
				ICompoundStatement thenStatement,
				ICompoundStatement elseStatement) {
			this.predicate = predicate;
			this.thenStatement = thenStatement;
			this.elseStatement = elseStatement;
		}

		IfThenElse(IBooleanExpression predicate,
				ICompoundStatement thenStatement) {
			this.predicate = predicate;
			this.thenStatement = thenStatement;
		}

		@Override
		public void accept(parser.IParse.IParseNodeVisitor visitor) {
			if (visitor.visit(this)) {
				predicate().accept(visitor);
				thenStatement().accept(visitor);
				if (elseStatement() != null) {
					elseStatement().accept(visitor);
				}
			}
		}

		@Override
		public ICompoundStatement elseStatement() {
			return elseStatement;
		}

		@Override
		public IBooleanExpression predicate() {
			return predicate;
		}

		@Override
		public ICompoundStatement thenStatement() {
			return thenStatement;
		}
	}

	private static class While extends SimpleStatement implements IWhile {
		private final IBooleanExpression predicate;
		private final ICompoundStatement body;

		While(IBooleanExpression predicate, ICompoundStatement body) {
			this.predicate = predicate;
			this.body = body;
		}

		@Override
		public void accept(parser.IParse.IParseNodeVisitor visitor) {
			if (visitor.visit(this)) {
				predicate.accept(visitor);
				body.accept(visitor);
			}
		}

		@Override
		public ICompoundStatement body() {
			return body;
		}

		@Override
		public IBooleanExpression predicate() {
			return predicate;
		}
	}

	private static class IntegerLiteral extends AbstractNode implements
			IIntegerLiteral {
		Integer value;

		IntegerLiteral(Integer value) {
			this.value = value;
		}

		@Override
		public void accept(IParseNodeVisitor visitor) {
			visitor.visit(this);
		}

		@Override
		public Integer value() {
			return value;
		}
	}

	public static class DecimalLiteral extends AbstractNode implements
			IDecimalLiteral {
		Double value;

		DecimalLiteral(Double value) {
			this.value = value;
		}

		@Override
		public void accept(parser.IParse.IParseNodeVisitor visitor) {
			visitor.visit(this);
		}

		@Override
		public Double value() {
			return value;
		}
	}

	public static class CompoundStatement extends AbstractNode implements
			ICompoundStatement {
		Collection<ISimpleStatement> statements;

		CompoundStatement(Collection<ISimpleStatement> statements) {
			this.statements = statements;
		}

		@Override
		public void accept(parser.IParse.IParseNodeVisitor visitor) {
			if (visitor.visit(this)) {
				for (ISimpleStatement statement : statements()) {
					statement.accept(visitor);
				}
			}
		}

		@Override
		public Collection<parser.IParse.ISimpleStatement> statements() {
			return statements;
		}
	}

	public static class Function extends AbstractNode implements IFunction {
		String name;
		List<IVariable> parameters;
		ICompoundStatement body;

		Function(String name, List<IVariable> parameters,
				ICompoundStatement body) {
			this.name = name;
			this.parameters = parameters;
			this.body = body;
		}

		@Override
		public void accept(parser.IParse.IParseNodeVisitor visitor) {
			if (visitor.visit(this)) {
				for (IVariable variable : parameters) {
					variable.accept(visitor);
				}
				body().accept(visitor);
			}
		}

		@Override
		public parser.IParse.ICompoundStatement body() {
			return body;
		}

		@Override
		public String name() {
			return name;
		}

		@Override
		public List<parser.IParse.IVariable> parameters() {
			return parameters;
		}
	}

	public static class Return extends SimpleStatement implements IReturn {
		IExpression expression;

		Return(IExpression expression) {
			this.expression = expression;
		}

		@Override
		public void accept(IParseNodeVisitor visitor) {
			if (visitor.visit(this)) {
				expression().accept(visitor);
			}
		}

		@Override
		public IExpression expression() {
			return expression;
		}
	}

	public static class Term extends Expression implements ITerm {
		Term(IExpression leftOperand, IToken operator, IExpression rightOperand) {
			super(leftOperand, operator, rightOperand);
		}

		@Override
		public void accept(IParseNodeVisitor visitor) {
			if (visitor.visit(this)) {
				leftOperand().accept(visitor);
				rightOperand().accept(visitor);
			}
		}
	}

	public static class Variable extends AbstractNode implements IVariable {
		String name;

		Variable(String name) {
			this.name = name;
		}

		@Override
		public void accept(parser.IParse.IParseNodeVisitor visitor) {
			visitor.visit(this);
		}

		@Override
		public String varId() {
			return String.format("T_%s", name);
		}

		@Override
		public String name() {
			// TODO Auto-generated method stub
			return name;
		}
	}
}
